/* 
 * File:   BlinnphongShader.cpp
 * Author: brady
 * 
 * Created on August 9, 2011, 7:33 PM
 */

#include <shaders/BlinnphongShader.h>

BlinnphongShader::BlinnphongShader() {
}

BlinnphongShader::BlinnphongShader(const Vector3D& specular, double phongExp, double r){
    phongExponent = phongExp;
    specularColor = specular;
    reflectivity = r;
}

BlinnphongShader::BlinnphongShader(const BlinnphongShader& orig) {
}

BlinnphongShader::~BlinnphongShader() {
}

Vector3D BlinnphongShader::shade(IntersectionResult& result) const {
    //the sum of the light hitting the object
    Vector3D highlights(0,0,0);
    Vector3D totalLight(0,0,0);
    Vector3D viewDir = result.ray.getDirection()*-1;
    const Vector3D& point(result.hitPoint);
    const vector<Light*>& lights = gParams.scene->lights;

    int lightCount = lights.size();
    for(int i = 0; i < lightCount; i++){
        Light* curLight = lights[i];
        Vector3D samplePoint = curLight->getSamplePoint(result);
        double shadowCoeff = 0;
            Vector3D toLight = samplePoint - point;
            double lightDistance = toLight.magnitude();
            toLight.normalize();

            Ray lightSampleRay = Ray(result.hitPoint, toLight);

            //Test for shadows
            //if lightray hits an object, shadow coeff = 0.0
            //otherwise compute the shadow coefficient to unobstructed light

            //Try limiting or disabling this section to rule out or in Shadow detection
            IntersectionParams shadowParams(lightSampleRay, FLT_EPSILON, lightDistance);
            IntersectionResult occluder = gParams.scene->intersectionStructure->getAnyIntersection(shadowParams);
            if (occluder.shape != NULL) {
                shadowCoeff = 0.0;
            }
            else {
                    shadowCoeff = getLambertianCoeffecient(result.normal, toLight);
                    Vector3D h = viewDir.bisect(toLight);
                    double c = h.dot(result.normal);
                    double highlightCoeff = pow(c, phongExponent);
                    highlights += specularColor.mix(curLight->getColor() * highlightCoeff);
            }
        totalLight += (curLight->getColor() * shadowCoeff);
    }

    Vector3D reflections = computeReflections(result);
    //the color of the object at the point of intersection
    Vector3D pointColor = result.shape->texture->getColor(result);
    Vector3D finalColor = pointColor.mix(totalLight).weightedAverage(reflections, reflectivity) + highlights;
    return finalColor;
}

Vector3D BlinnphongShader::computeHighlights(IntersectionResult& result) const{
    const Vector3D& point(result.hitPoint);
    const Vector3D& viewDir(result.ray.getDirection() * -1);
    const vector<Light*>& lights = gParams.scene->lights;

    Vector3D highlights = Vector3D(0.0, 0.0, 0.0);
    int lightCount = lights.size();
    for(int i = 0; i < lightCount; i++){
        Light* curLight = lights[i];
        Vector3D samplePoints = curLight->getSamplePoint(result);

        Vector3D toLight = samplePoints - point;
        toLight.normalize();
        Vector3D h = viewDir.bisect(toLight);
        double c = h.dot(result.normal);
        double highlightCoeff = pow(c, phongExponent);
        highlights += specularColor.mix(curLight->getColor() * highlightCoeff);
        }
    return highlights;
}

Vector3D BlinnphongShader::computeReflections(IntersectionResult& result) const{
    const Vector3D& hitPoint = result.hitPoint;
    Vector3D reflection = Vector3D(0.0, 0.0, 0.0);
    if (reflectivity > 0.0 && result.ray.recursionLimit > 0) {
        Ray reflectedRay = result.ray.reflect(result.normal, hitPoint);
        IntersectionParams ip(reflectedRay, FLT_EPSILON, 100 ); //TODO make tmax a parameter
        IntersectionResult reflectionResult = gParams.scene->intersectionStructure->getFirstIntersection(ip);
        if(reflectionResult.shape != NULL){
            reflection = reflectionResult.shape->getShader()->shade(reflectionResult);
        }
        else{
            reflection = gParams.scene->backgroundColor;
        }
    }
    return reflection;
}